 /* Copyright (C)
  * 2024 ZKLX Optoelectronic Technology Co.LTD
  * All rights reserved.
  */
 /**
  * @file web_viewer.cpp
  * @brief jcvp web engine
  * @author xuke
  * @version 1.0
  * @date 2024-10-15
  */

#include <iostream>
#include <thread>
#include <unistd.h>
#include <opencv2/opencv.hpp>
#include <jcvp_web/jcvp.h>
#include "global_info.h"
#include "frame_render.h"
#include "algorithm_api.h"
#include "cam_capture.h"
#include "servo_comm.h"
#include "cJSON.h"
#include "zmq_msg_process.h"
#include "config.h"

#include "lx_elog/finsh.h"
#define LOG_TAG web_viewer
#include "lx_elog/stor_elog.h"

/*--------------------------------extern--------------------------------------------*/
extern Global &global;
extern FrameRender g_frame_render;
extern ALGORITHM_API_T g_algorithm_api;
extern uint32_t g_record_frame;

/*--------------------------------globals-------------------------------------------*/
JCVP_T *g_jcvp_obj = NULL;
bool g_is_nfs_mounted = false;
std::string g_offset_filename = "/home/lx/rec_offset_YYMMDD_HHMMSS.csv";
bool g_offset_output_en = false;

/*--------------------------------locals--------------------------------------------*/

/*--------------------------------funcs---------------------------------------------*/
// video play output
static void jcvp_vp_output_task(void *args)
{
	cv::Mat out_img;

	while (true) {
		if (g_jcvp_obj && g_jcvp_obj->_conn) {
			g_frame_render.getFrame(out_img);
			g_jcvp_obj->frame_update(out_img);
		}

		usleep(10*1000);
	}
}

// json ctrl reciver
static void jcvp_jc_recive_task(void *args)
{
	std::string jc_str;
	char *cmd_type = NULL;

	while (true) {
		if (!g_jcvp_obj->_conn) { //is connected
			sleep(1);
			continue;
		}

		jc_str = g_jcvp_obj->jc_recv();
		//log_d("jc:[%s]", jc_str.c_str());

		cJSON *root = cJSON_Parse(jc_str.c_str());
		if (!root) {
			log_e("Error json format: [%s]", cJSON_GetErrorPtr());
			continue;
		}
		cmd_type = XK_JSON_GET_STRING(root, "type");
		if (!cmd_type) {
			continue;
		}
		if (!strcmp(cmd_type, "rect")) {
			cJSON *sub_obj = XK_JSON_GET_OBJ(root, cmd_type);
			cv::Rect jc_rect{XK_JSON_GET_INTEGER(sub_obj, "x"), XK_JSON_GET_INTEGER(sub_obj, "y"), XK_JSON_GET_INTEGER(sub_obj, "w"), XK_JSON_GET_INTEGER(sub_obj, "h")};
			//正向化rect
			if (jc_rect.width < 0) {
				jc_rect.width = -jc_rect.width;
				jc_rect.x -= jc_rect.width;
			}
			if (jc_rect.height < 0) {
				jc_rect.height = -jc_rect.height;
				jc_rect.y -= jc_rect.height;
			}
			log_i("use [rect]: [%d, %d, w%d, h%d]", jc_rect.x, jc_rect.y, jc_rect.width, jc_rect.height);

			global.set_isTargetRectSet(true);
			g_algorithm_api.resetBoundTarget();
			global.set_targetRect(jc_rect.x, jc_rect.y, jc_rect.width, jc_rect.height);
			global.set_trackMode(Global::TrackMode::SINGLE_TARGET);
		} else if (!strcmp(cmd_type, "point")) {
			cJSON *sub_obj = XK_JSON_GET_OBJ(root, cmd_type);

			global.set_isTargetCoordSet(true);
			global.set_isTargetRectSet(false);
			global.set_targetCoord({XK_JSON_GET_INTEGER(sub_obj, "x"), XK_JSON_GET_INTEGER(sub_obj, "y")});
			global.set_trackMode(Global::TrackMode::SINGLE_TARGET);
			log_i("get [point]: [%d, %d]", XK_JSON_GET_INTEGER(sub_obj, "x"), XK_JSON_GET_INTEGER(sub_obj, "y"));
		} else if (!strcmp(cmd_type, "data_ctl")) {
			char *args_name = XK_JSON_GET_STRING(root, "cmd");
			if (!strcmp(args_name, "nfs_mount")) {
				char *nfs_dir = cJSON_GetObjectItem(root, "dir")->valuestring;
				log_d("get nfs dir:[%s]", nfs_dir);
				std::string mount_cmd = "sudo mkdir -p /mnt/nfs_data && sudo mount -t nfs -o nolock " + std::string(nfs_dir) + " /mnt/nfs_data";
				if (!system(mount_cmd.c_str())) {
					g_is_nfs_mounted = true;
				}
			} else if (!strcmp(args_name, "rec_raw")) {
				uint32_t rec_num = XK_JSON_GET_INTEGER(root, "frame_num");
				start_data_record(rec_num);
			}
		} else if (!strcmp(cmd_type, "zt_ctrl")) {
			char *args_name = XK_JSON_GET_STRING(root, "cmd");
			if (!strcmp(args_name, "zt_move")) {
				cJSON *sub_obj = XK_JSON_GET_OBJ(root, args_name);
				servo_comm_send_speed(cJSON_GetObjectItem(sub_obj, "x")->valuedouble, cJSON_GetObjectItem(sub_obj, "y")->valuedouble);
			} else if (!strcmp(args_name, "zt_enable")) {
				servo_comm_enable(cJSON_GetObjectItem(root, args_name)->valueint);
			}
		} else if (!strcmp(cmd_type, "lens_ctrl")) {
			char *args_name = XK_JSON_GET_STRING(root, "cmd");
			if (!strcmp(args_name, "zoom_add")) {
				lens_ctrl_zoom(1, 0);
			} else if (!strcmp(args_name, "zoom_sub")) {
				lens_ctrl_zoom(-1, 0);
			} else if (!strcmp(args_name, "zoom_stop")) {
				lens_ctrl_zoom(0, 0);
			} else if (!strcmp(args_name, "a_zoom_en")) {
				log_d("set auto_zoom_en: %s", cJSON_GetObjectItem(root, args_name)->valueint ? "true" : "false");
				global.auto_zoom_en = cJSON_GetObjectItem(root, args_name)->valueint;
			} else if (!strcmp(args_name, "focus_add")) {
				lens_ctrl_focus(1, 0);
			} else if (!strcmp(args_name, "focus_sub")) {
				lens_ctrl_focus(-1, 0);
			} else if (!strcmp(args_name, "focus_stop")) {
				lens_ctrl_focus(0, 0);
			} else if (!strcmp(args_name, "a_focus_en")) {
				log_d("set auto_zoom_en: %s", cJSON_GetObjectItem(root, args_name)->valueint ? "true" : "false");
			} else if (!strcmp(args_name, "apert_add")) {
				lens_ctrl_apert(1, 0);
			} else if (!strcmp(args_name, "apert_sub")) {
				lens_ctrl_apert(-1, 0);
			} else if (!strcmp(args_name, "apert_stop")) {
				lens_ctrl_apert(0, 0);
			} else if (!strcmp(args_name, "a_apert_en")) {
				log_d("set auto_zoom_en: %s", cJSON_GetObjectItem(root, args_name)->valueint ? "true" : "false");
			}
		} else if (!strcmp(cmd_type, "test_arg")) {
			char *args_name = XK_JSON_GET_STRING(root, "cmd");
			if (!strcmp(args_name, "ctv_zoom")) {
				log_d("set ctv zoom: %f", cJSON_GetObjectItem(root, args_name)->valuedouble);
				global.set_focalLength(cJSON_GetObjectItem(root, args_name)->valuedouble);
			} else if (!strcmp(args_name, "rec_offset")) {
				if (cJSON_GetObjectItem(root, args_name)->valueint) { //start
					time_t cur_time = time(NULL);
					char cur_timestamp[64] = {0};
					strftime(cur_timestamp, sizeof(cur_timestamp), "%Y%m%d_%H%M%S", localtime(&cur_time));
					g_offset_filename = "/home/lx/rec_offset_" + std::string(cur_timestamp) + ".csv";
					g_offset_output_en = true;
				} else { //stop
					g_offset_output_en = false;
				}
			} else if (!strcmp(args_name, "a_track_th")) {
				log_d("set auto_track_threshold: %d", cJSON_GetObjectItem(root, args_name)->valueint);
				global.set_autoTrackThreshold(cJSON_GetObjectItem(root, args_name)->valueint);
			} else if (!strcmp(args_name, "a_track_en")) {
				log_d("set auto_track_en: %s", cJSON_GetObjectItem(root, args_name)->valueint ? "true" : "false");
				global.auto_track_en = cJSON_GetObjectItem(root, args_name)->valueint;
			} else if (!strcmp(args_name, "l_search_en")) {
				log_d("set local_search_en: %s", cJSON_GetObjectItem(root, args_name)->valueint ? "true" : "false");
				global.local_search_en = cJSON_GetObjectItem(root, args_name)->valueint;
				if (global.local_search_en) {
					/* ZklxTag: xuke modified on 2025/02/18 for fixing M# 初始化air_points */
					global.air_points[0].x = global.get_ttbYaw()   + global.get_angle_by_pic(0.0);                    //point_0
					global.air_points[0].y = global.get_ttbPitch() + global.get_angle_by_pic(0.0);
					global.air_points[1].x = global.get_ttbYaw()   + global.get_angle_by_pic(-1.0*CLI_MAX_IMG_WIDTH); //point_1
					global.air_points[1].y = global.get_ttbPitch() + global.get_angle_by_pic(0.0);
					global.air_points[2].x = global.get_ttbYaw()   + global.get_angle_by_pic(-1.0*CLI_MAX_IMG_WIDTH); //point_2
					global.air_points[2].y = global.get_ttbPitch() + global.get_angle_by_pic(1.0*CLI_MAX_IMG_HEIGHT);
					global.air_points[3].x = global.get_ttbYaw()   + global.get_angle_by_pic(1.0*CLI_MAX_IMG_WIDTH);  //point_3
					global.air_points[3].y = global.get_ttbPitch() + global.get_angle_by_pic(1.0*CLI_MAX_IMG_HEIGHT);
					global.air_points[4].x = global.get_ttbYaw()   + global.get_angle_by_pic(1.0*CLI_MAX_IMG_WIDTH);  //point_4
					global.air_points[4].y = global.get_ttbPitch() + global.get_angle_by_pic(-1.0*CLI_MAX_IMG_HEIGHT);
					global.air_points[5].x = global.get_ttbYaw()   + global.get_angle_by_pic(-1.0*CLI_MAX_IMG_WIDTH); //point_5
					global.air_points[5].y = global.get_ttbPitch() + global.get_angle_by_pic(-1.0*CLI_MAX_IMG_HEIGHT);
					for (int i=0; i<6; i++) {
						log_d("set air_points[%d] = (%f, %f)", i, global.air_points[i].x, global.air_points[i].y);
					}
					global.air_point_state = 0;
					servo_comm_enable(true);
				}
			}
		}
		cJSON_Delete(root); //free
	}
}

// json status sender
static void jcvp_js_update_task(void *args)
{
	char date_str[32] = {0};
	char time_str[32] = {0};

	while (true) {
		if (g_jcvp_obj->_conn) { //is connected
			cJSON *root = cJSON_CreateObject();

			//root base
			if (1) {
				time_t cur_time = time(NULL);
				strftime(date_str, sizeof(date_str), "%Y-%m-%d", localtime(&cur_time));
				strftime(time_str, sizeof(time_str), "%H:%M:%S", localtime(&cur_time));
				cJSON_AddStringToObject(root, "date", date_str);
				cJSON_AddStringToObject(root, "time", time_str);
			}
			//js_record_data
			if (1) {
				cJSON *record_state_obj = cJSON_CreateObject();
				cJSON_AddItemToObject(root, "record_state", record_state_obj);
				cJSON_AddNumberToObject(record_state_obj, "cur", 1000 - g_record_frame);
				cJSON_AddNumberToObject(record_state_obj, "all", 1000);
				cJSON_AddBoolToObject(record_state_obj, "nfs", g_is_nfs_mounted);
			}
			//js_algo_args
			if (1) {
				cJSON *algo_args_obj = cJSON_CreateObject();
				cJSON_AddItemToObject(root, "algo_args", algo_args_obj);
				cJSON_AddNumberToObject(algo_args_obj, "a_track_th", global.get_autoTrackThreshold());
			}

			char *root_str = cJSON_Print(root);
			g_jcvp_obj->jc_send(std::string(root_str));
			free(root_str);
		}

		sleep(1);
	}
}

int web_viewer_init(std::string web_root, int port, int fps, float img_scaling)
{
	// init jcvp_web server
	g_jcvp_obj = new JCVP_T(web_root, port, fps, img_scaling); //web_root, fps, img_scaling
	g_jcvp_obj->webs_run();

	// vp task
	std::thread jcvp_vp_thread(jcvp_vp_output_task, nullptr);
	jcvp_vp_thread.detach();

	// jc task
	std::thread jcvp_jc_thread(jcvp_jc_recive_task, nullptr);
	jcvp_jc_thread.detach();

	// js task
	std::thread jcvp_js_thread(jcvp_js_update_task, nullptr);
	jcvp_js_thread.detach();

	return 0;
}

SPDK_LOG_REGISTER_COMPONENT(web_viewer)
